package pira.parser.xml.citeseerx;

import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileReader;
import java.util.Map;
import java.util.NoSuchElementException;

import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.index.IndexManager;
import org.neo4j.kernel.EmbeddedGraphDatabase;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import pira.constants.PIRAConfig;
import pira.constants.PIRAConfig.MyRelationshipTypes;
import pira.elements.Paper;
import pira.utils.Log;

public class CitationCounter extends DefaultHandler {
	// use to gather data from characters SAX
	private CharArrayWriter contents = new CharArrayWriter();

	// program log file
	private Log log;
	private Log error;

	// transaction for database
	private Transaction tx;

	// graph database service
	private GraphDatabaseService graphDb;

	// database index
	private IndexManager index;

	// index for papers
	public Index<Node> papers;

	// index for authors
	public Index<Node> authors;

	// file reader
	private FileReader fr;

	private Node currentNode;
	private String currentPaperID;
	private Node destNode;
//	private String destTitle;
	private Paper citedPaper;
	
	// constant 
	public static final String PAPERS = "papers";
	public static final String AUTHORS = "authors";
	public static final String DOCUMENT = "document";
	public static final String PAPERID = "paperid";
	public static final String CITATIONS = "citations";
	public static final String CITATION = "citation";
	public static final String CLUSTERID = "clusterid";
	public static final String TITLE = "title";
	public static final String YEAR = "year";
	
	static long startTime;
	static long paperCount = 0;
	static long citationCount = 0;

	private static boolean citaInfo = false;
	private static boolean chosen = false;
	
	public CitationCounter() {
		log = new Log("Citation_Log.txt");
		error = new Log("Citation_Error.txt");
		Map<String, String> configuration = EmbeddedGraphDatabase
				.loadConfigurations(PIRAConfig.CONFIG);
		graphDb = new EmbeddedGraphDatabase(PIRAConfig.DBPATH, configuration);
		tx = graphDb.beginTx();
		try {
			index = graphDb.index();

			this.papers = index.forNodes(PAPERS);
			this.authors = index.forNodes(AUTHORS);

			tx.success();
		} finally {
			tx.finish();
		}
	}

	@Override
	public void startElement(String namespaceURI, String localName,
			String qName, Attributes attr) throws SAXException {
		contents.reset();
		// get the paper ID
		if (localName.equals(DOCUMENT)) {
			// not citation info at first
			citaInfo = false;
			
			// create a new instance and add it to database
			if (attr.getLength() > 0) {
				String paperid = attr.getValue(PIRAConfig.ID);
				currentPaperID = paperid;
				
				try {
					currentNode = papers.get(Paper.PAPERID, paperid).getSingle();
				} catch (NoSuchElementException e) {
					error.write("Paperid : More than one element : " + paperid + "\n");
				}
			}
		}
		
		if (localName.equals(CITATIONS)) {
			// citation info begins
			citaInfo = true;
		}
		
		if (localName.equals(CITATION)) {
			// not chosen
			chosen = false;
			
			// set citedPaper
			citedPaper = new Paper();
		}

	}

	@Override
	public void endElement(String namespaceURI, String localName, String qName)
			throws SAXException {
		if (localName.equals(CLUSTERID)) {
			if (citaInfo && !chosen) {
				String clusterid = contents.toString();
				citedPaper.setClusterID(clusterid);
				try {
					destNode = papers.get(Paper.CLUSTERID, clusterid).getSingle();
				} catch (NoSuchElementException e) {
					error.write("More than one element for clusterid : " + clusterid + ".\n");
				}

				if (destNode != null)
					chosen = true;
				
			}
		}
		
		if (localName.equals(AUTHORS)) {
			if (citaInfo) {
				String[] authors = contents.toString().split(",");
			}
		}
		
		if (localName.equals(TITLE)) {
			if (citaInfo && !chosen) {
				String title = contents.toString();
				citedPaper.setTitle(title);
				try {
					destNode = papers.get(Paper.TITLE, title).getSingle();
				} catch (NoSuchElementException e) {
					error.write("More than one element for title : " + title + ".\n");
				}
				
				if (destNode != null) 
					chosen = true;
			}
		}
		
		if (localName.equals(YEAR)) {
			if (citaInfo) {
				String year = contents.toString();
				citedPaper.setYear(year);
			}
		}
		
		if (localName.equals(CITATION)) {
			if (chosen) {
				if (destNode != null) {
					currentNode.createRelationshipTo(destNode, MyRelationshipTypes.CITE);
					citationCount++;
				} else
					error.write("Can't find \"" + citedPaper.getTitle() + "\" at " + currentPaperID + "\n");
			} else {
				
			}
		}
	}

	@Override
	public void characters(char[] ch, int start, int length)
			throws SAXException {
		contents.write(ch, start, length);
	}

	private void readFile(File xmlFile) {
		try {
			// create SAX 2 parser
			XMLReader xr = XMLReaderFactory.createXMLReader();

			// create file reader
			fr = new FileReader(xmlFile);

			// install ContentHandler．．．
			xr.setContentHandler(this);

			// parse file
			xr.parse(new InputSource(fr));
			
			paperCount++;
			
			// finish transaction
			if (paperCount % 10000 == 0) {

				globalRecord();

				// restart the transaction
				tx.success();
				tx.finish();
				tx = graphDb.beginTx();

			}

		} catch (IllegalStateException e1) {
			System.out.println("Index has been shut down.");
			System.out.println("Transformer from XML to DB end.");
		} catch (Exception e) {
			if (paperCount % 10000 == 0) {

				globalRecord();

				// restart the transaction
				tx.success();
				tx.finish();
				tx = graphDb.beginTx();
			}
		}
	}

	private void readDirectory(File directory, int depth) {
		File[] list = directory.listFiles();
		for (File f : list) {
			if (f.isDirectory()) {
				if (depth < 4)
					readDirectory(f, depth + 1);
				else {
					File[] subversions = f.listFiles();
					if (subversions.length == 1) {
						readFile(subversions[0]);
					} else if (subversions.length == 2) {
						int l0 = subversions[0].getName().length();
						int l1 = subversions[1].getName().length();
						if (l0 > l1)
							readFile(subversions[0]);
						else
							readFile(subversions[1]);
					} else {
						File choice = subversions[0];
						int maxVersion = 0;
						for (File file : subversions) {
							int index = file.getName().indexOf('v');
							if (index != -1) {
								int version = -1;
								try {
									version = Integer.parseInt(file.getName()
											.substring(index + 1, index + 2));
								} catch (NumberFormatException e) {
									error.write(file.getName() + "\n");
								}
								if (version > maxVersion) {
									maxVersion = version;
									choice = file;
								}
							} else
								continue;
						}
						readFile(choice);
					}
				}

			} else {
				readFile(f);
			}
		}
	}

	private void globalRecord() {
		// store global info in global node
		// record informations
		long elapsedTime = (System.currentTimeMillis() - startTime) / 1000;
		System.out.println(paperCount);
		System.out.println("Time passed : " + elapsedTime + "s.");

		String str = paperCount + "\t" + elapsedTime + "\t" + citationCount
				+ "\n";
		log.write(str);
	}

	public void run() {
		startTime = System.currentTimeMillis();
		File root = new File(PIRAConfig.XMLPATH);
		readDirectory(root, 0);
		System.out.println("Citation count is : " + citationCount);
		shutDown();
	}
	
	private void shutDown() {
		System.out.println("Program ends ...");
		log.close();
		error.close();
		tx.success();
		tx.finish();
		graphDb.shutdown();
	}
	
	public static void main(String[] args) {
		new CitationCounter().run();
	}
}
